package jmine.tec.hydra.domain.actions;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;

import jmine.tec.hydra.HydraController;
import jmine.tec.hydra.api.HydraAction;
import jmine.tec.hydra.api.HydraMessage;
import jmine.tec.hydra.api.HydraRequest;
import jmine.tec.hydra.api.HydraRequestVisitor;
import jmine.tec.hydra.api.HydraSpecification;
import jmine.tec.hydra.domain.HydraComment;
import jmine.tec.hydra.domain.HydraRequestImpl;
import jmine.tec.hydra.domain.HydraSystem;
import jmine.tec.hydra.domain.enumx.HydraActionStatus;
import jmine.tec.hydra.domain.enumx.HydraCommentOrigin;
import jmine.tec.persist.annotation.Alias;
import jmine.tec.persist.annotation.Comment;
import jmine.tec.persist.annotation.Comments;
import jmine.tec.persist.annotation.Constraint;
import jmine.tec.persist.annotation.Index;

import org.apache.commons.lang.builder.CompareToBuilder;
import org.apache.log4j.Logger;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import org.hibernate.annotations.CollectionOfElements;
import org.hibernate.annotations.IndexColumn;
import org.hibernate.annotations.Type;
import org.hibernate.validator.NotNull;

import bancosys.tec.component.Documentation;
import bancosys.tec.persist.bussobj.PersistableBusinessObject;
import bancosys.tec.utils.date.Timestamp;

/**
 * Classe abstrata de implementações de ações resultantes de uma chamada ao sistema Hydra.
 * 
 * @author lundberg
 */
@Entity
@Alias("HYACTI")
@Table(name = "HYDRA_ACTION")
@DiscriminatorColumn(name = "TP_HYDRA_ACTION", discriminatorType = DiscriminatorType.INTEGER)
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@SequenceGenerator(name = "SEQ_HYACTI", sequenceName = "SEQ_HYACTI")
@Documentation("ARMAZENA OS REGISTROS DE ACOES EXECUTADAS NO HYDRA.")
@Comment(table = "HYDRA_ACTION", column = "TP_HYDRA_ACTION", value = "TIPO DE ACTION HYDRA.")
@Constraint(suffix = "1")
public abstract class AbstractHydraAction extends PersistableBusinessObject implements HydraAction, Comparable<AbstractHydraAction> {

    public static final String MENSAGEM_SUCESSO = "Ação finalizada com status '%s'.";

    public static final String MENSAGEM_FALHA = "Ação finalizada com falha.";

    protected static final Logger LOGGER = Logger.getLogger(HydraAction.class);

    private Long id;

    private HydraRequest request;

    private Timestamp actionStart;

    private Timestamp actionEnd;

    private HydraActionStatus status;

    private List<HydraComment> comments = new ArrayList<HydraComment>();

    private String description;

    // Transient
    private HydraRequestVisitor visitor;

    /**
     * {@inheritDoc}
     */
    public final List<HydraAction> execute(HydraRequest req, HydraRequestVisitor requestVisitor) throws Throwable {
        this.request = req;
        this.visitor = requestVisitor;
        LOGGER.debug(String.format("Iniciando execução de HydraAction %s.", this.getDescription()));
        this.onStart();
        LOGGER.debug(String.format("Inicialização de HydraAction %s ocorreu com sucesso.", this.getDescription()));
        try {
            List<HydraAction> actions = this.doExecute();
            LOGGER.debug(String.format("Execução de HydraAction %s ocorreu com sucesso.", this.getDescription()));
            this.onSuccess();
            LOGGER.debug(String.format("Sucesso de HydraAction %s registrado com sucesso.", this.getDescription()));
            return actions;
        } catch (Throwable t) {
            try {
                this.onFailure(t);
            } catch (Throwable t2) {
                // Falha em salvar status de falha, logando no RTM para que a informação não se perca e jogando exception original para o
                // handler.
                LOGGER.error(String.format("Erro salvando status de falha.", this.getDescription()));
                this.getController().getRtmController().monitor(t2);
            }
            throw t;
        }
    }

    /**
     * Efetua a ação do HydraAction específico.
     * 
     * @return List de HydraAction
     * @throws Exception caso ocorra qualquer falha
     */
    protected abstract List<HydraAction> doExecute() throws Exception;

    /**
     * Atualiza a ação ao falhar
     * 
     * @param t failure
     */
    protected void onFailure(final Throwable t) {
        this.logAsSystem(t.getMessage());
        this.logAsSystem(MENSAGEM_FALHA);
        this.setStatus(HydraActionStatus.FAILED);
        this.setActionEnd(new Timestamp());
    }

    /**
     * Atualiza a ação após um sucesso.
     */
    protected void onSuccess() {
        if (this.getStatus().initialized()) {
            this.setStatus(HydraActionStatus.SUCCESS);
        }
        this.logAsSystem(String.format(MENSAGEM_SUCESSO, AbstractHydraAction.this.getStatus().getName()));
        this.setActionEnd(new Timestamp());
    }

    /**
     * Atualiza a ação ao iniciar a execução.
     */
    protected void onStart() {
        this.setStatus(HydraActionStatus.INITIALIZED);
        this.setDescription(AbstractHydraAction.this.createDescription());
        this.setActionStart(new Timestamp());
    }

    /**
     * Cria a descrição da action
     * 
     * @return String
     */
    protected abstract String createDescription();

    /**
     * Adiciona um log à action feito pelo sistema. Usualmente para mensagens técnicas.
     * 
     * @param comment comment
     */
    protected void logAsSystem(String comment) {
        this.getComments().add(new HydraComment(HydraCommentOrigin.SYSTEM, new Timestamp(), "HYDRA", comment));
    }

    /**
     * Adiciona um log à action feito pelo usuário. Usualmente para mensagens relativas ao negócio.
     * 
     * @param comment comment
     */
    protected void logAsUser(String comment) {
        this.getComments().add(new HydraComment(HydraCommentOrigin.USER, new Timestamp(), "HYDRA", comment));
    }

    /**
     * {@inheritDoc}
     */
    public void markSolved() {
        this.setStatus(HydraActionStatus.SOLVED);
    }

    /**
     * @return the id
     */
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_HYACTI")
    @Column(name = "COD_HYDRA_ACTION", nullable = false)
    @Documentation("CODIGO DA ACTION EXECUTADA NO HYDRA.")
    public Long getId() {
        return this.id;
    }

    /**
     * @return the requestStart
     */
    @NotNull
    @Column(name = "ACTION_START", nullable = false)
    @Type(type = "bancosys.tec.persist.hibernate.type.TimestampType")
    @Documentation("TEMPO DE INICIO DA ACTION.")
    public Timestamp getActionStart() {
        return this.actionStart;
    }

    /**
     * @return the requestEnd
     */
    @Column(name = "ACTION_END")
    @Type(type = "bancosys.tec.persist.hibernate.type.TimestampType")
    @Documentation("TEMPO EM QUE A EXECUCAO DA ACTION TERMINOU.")
    public Timestamp getActionEnd() {
        return this.actionEnd;
    }

    /**
     * @return the status
     */
    @NotNull
    @Column(name = "STATUS", nullable = false)
    @Documentation("STATUS DA ACTION. 0 - INICIADO; 1 - SUCESSO; 2 - FALHA; 3 - PENDENTE; 4 - RESOLVIDO.")
    @Constraint(suffix = "0")
    public HydraActionStatus getStatus() {
        return this.status;
    }

    /**
     * @return the comments
     */
    @Index(suffix = "JT_6", columns = "COD_HYDRA_ACTION")
    @Alias("HYACLG")
    @CollectionOfElements
    @Cascade({ CascadeType.ALL, CascadeType.DELETE_ORPHAN })
    @JoinTable(name = "HYDRA_ACTION_LOG", joinColumns = @JoinColumn(name = "COD_HYDRA_ACTION"))
    @IndexColumn(name = "IND_LOG")
    @Comments({ @Comment(table = "HYDRA_ACTION_LOG", value = "LOG DE EXECUCAO DE UM ACTION NO HYDRA"),
            @Comment(table = "HYDRA_ACTION_LOG", column = "IND_LOG", value = "INDICE DO LOG DE EXECUCAO DE UMA ACTION NO HYDRA."),
            @Comment(table = "HYDRA_ACTION_LOG", column = "COD_HYDRA_ACTION", value = "CODIGO DA ACTION HYDRA QUE CONTEM O COMENTARIO."),
            @Comment(table = "HYDRA_ACTION_LOG", column = "AUTOR_COMMENT", value = "NOME DO AUTOR DO COMENTARIO."),
            @Comment(table = "HYDRA_ACTION_LOG", column = "CONTENT", value = "CONTEUDO DO COMENTARIO."),
            @Comment(table = "HYDRA_ACTION_LOG", column = "ORIG_COMMENT", value = "ORIGEM DO COMENTARIO. 0 - SISTEMA; 1 - USUARIO."),
            @Comment(table = "HYDRA_ACTION_LOG", column = "TIME_COMMENT", value = "HORA EM QUE O COMENTARIO FOI FEITO.") })
    public List<HydraComment> getComments() {
        return this.comments;
    }

    /**
     * Obtém a referência sendo executada. Valor disponível exclusivamente durante a execução da ação.
     * 
     * @return request
     */
    @Index(suffix = "JT_7")
    @ManyToOne(fetch = FetchType.LAZY, targetEntity = HydraRequestImpl.class)
    @JoinColumn(name = "COD_HYDRA_REQUEST", nullable = false)
    @Documentation("CODIGO DA REQUISICAO QUE GEROU ESTA ACAO.")
    public HydraRequest getRequest() {
        return this.request;
    }

    /**
     * @return the description
     */
    @NotNull
    @Column(name = "DESCR", nullable = false)
    @Documentation("DESCRICAO DA ACTION")
    public String getDescription() {
        return this.description;
    }

    /**
     * @param id the id to set
     */
    public void setId(Long id) {
        this.id = id;
    }

    /**
     * @param actionStart the actionStart to set
     */
    public void setActionStart(Timestamp actionStart) {
        this.actionStart = actionStart;
    }

    /**
     * @param actionEnd the actionEnd to set
     */
    public void setActionEnd(Timestamp actionEnd) {
        this.actionEnd = actionEnd;
    }

    /**
     * @param status the status to set
     */
    public void setStatus(HydraActionStatus status) {
        this.status = status;
    }

    /**
     * @param comments the comments to set
     */
    public void setComments(List<HydraComment> comments) {
        this.comments = comments;
    }

    /**
     * @param description the description to set
     */
    public void setDescription(String description) {
        this.description = description;
    }

    /**
     * @param request the request to set
     */
    public void setRequest(HydraRequest request) {
        this.request = request;
    }

    /**
     * {@inheritDoc}
     */
    public int compareTo(AbstractHydraAction o) {
        return new CompareToBuilder().append(this.id, o.id).toComparison();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @Transient
    public HydraController getController() {
        return (HydraController) super.getController();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return String.format("%s: Status=%s", this.getDescription(), this.getStatus());
    }

    /**
     * @param identification identification
     * @param message message
     * @see jmine.tec.hydra.api.HydraRequestVisitor#saveMessageWithConversation(java.lang.String, jmine.tec.hydra.api.HydraMessage)
     */
    protected void saveMessageWithConversation(String identification, HydraMessage message) {
        this.visitor.saveMessageWithConversation(identification, message);
    }

    /**
     * @param message message
     * @see jmine.tec.hydra.api.HydraRequestVisitor#saveMessage(jmine.tec.hydra.api.HydraMessage)
     */
    protected void saveMessage(HydraMessage message) {
        this.visitor.saveMessage(message);
    }

    /**
     * @param summary summary
     * @see jmine.tec.hydra.api.HydraRequestVisitor#setSummary(java.lang.String)
     */
    protected void setSummary(String summary) {
        this.visitor.setSummary(summary);
    }

    /**
     * @param system system
     * @see jmine.tec.hydra.api.HydraRequestVisitor#setSystem(jmine.tec.hydra.domain.HydraSystem)
     */
    protected void setSystem(HydraSystem system) {
        this.visitor.setSystem(system);
    }

    /**
     * @param specification specification
     * @see jmine.tec.hydra.api.HydraRequestVisitor#setSpecification(jmine.tec.hydra.api.HydraSpecification)
     */
    protected void setSpecification(HydraSpecification specification) {
        this.visitor.setSpecification(specification);
    }

    /**
     * @see jmine.tec.hydra.api.HydraRequestVisitor#markAsPending()
     */
    protected void markAsPending() {
        this.status = HydraActionStatus.PENDING;
        this.visitor.markAsPending();
    }
}
